﻿#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#define CLEAR "cls"//在windows系统下的清屏命令
#else
#define CLEAR "clear"//在其他系统下的清屏命令
#endif
//预处理指令和头文件
typedef struct Train {
    char id[10];
    char start[20];
    char end[20];
    char departure[10];
    char arrival[10];
    float price;
    int tickets;
    struct Train* next;//定义了一个指向  struct Train  类型的指针  next ，这个指针用于将多个 
    // Train  结构体连接起来，形成链表结构。每个  Train  结构体节点通过  next  指针指向下一个节点
    // ，最后一个节点的  next  指针通常设置为  NULL ，表示链表的结束。
} Train;

Train* head = NULL;
//结构体定义Train用于存储车次的相关信息，包括车次ID、始发站、终点站、出发时间、到达时间、票价
//剩 余票数以及指向下一个节点的指针。同时定义了一个全局的链表头指针 head ，初始化为 NULL 。

//安全读取字符串
void inputString(char* prompt, char* buffer, int size)//该函数用于安全地从标准输入读取字符串，
                                                      //接收提示信息、目标缓冲区和缓冲区大小作为参数。
    //最重要！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！
    //！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！
{
    printf("%s", prompt);
    fgets(buffer, size, stdin);
    buffer[strcspn(buffer, "\n")] = '\0';  // 这行代码使用了  strcspn  函数， strcspn  函数会在字符串  buffer  中查找
                                   //第一个与  "\n"  中的字符匹配的位置，并返回从字符串开头到该位置的字符数。因为  fgets  
                                   //会把换行符读取到缓冲区中，所以通过  strcspn  找到换行符的位置后，将该位置的字符替换
                                   //为字符串结束符  '\0'  ，这样就移除了缓冲区中的换行符，使读取到的字符串更加符合我们
                                   //的使用习惯（通常不需要换行符在字符串中）。
}
//该函数用于安全地从标准输入读取字符串，接收提示信息、目标缓冲区和缓冲区大小作为参数。
//  使用 fgets 读取字符串，然后移除字符串末尾的换行符

// 添加车次
void AddTrain() //AddTrain函数实现了在链表中添加新的车次节点的功能
                //同时会检查车次是否已存在，确保数据的唯一性。
{
    Train* p = (Train*)malloc(sizeof(Train));
    char temp[100];
    //这行代码使用malloc函数为一个新的Train结构体类型的变量分配内存，并将分配内存的地址赋值给指针p 
    //这样p就指向了一个新的Train结构体，后续可以用来存储车次的相关信息。
    //char temp[100];声明了一个字符数组temp，用于临时存储从用户输入读取的字符串，
    //比如读取票价和剩余票数时先将输入存储在temp中，然后再进行类型转换
    inputString("请输入车次: ", p->id, sizeof(p->id));
    Train* cur = head;
    while (cur) //只要cur不为NULL就循环
    {
        if (strcmp(cur->id, p->id) == 0) //strcmp比较是否相同
        {
            printf("车次已存在，添加失败！\n");
            free(p);//释放之前为新节点p分配的内存
            return;
        }
        cur = cur->next;
    }
    //检查车次是否已经存在
    inputString("始发站: ", p->start, sizeof(p->start));
    inputString("终点站: ", p->end, sizeof(p->end));
    inputString("出发时间: ", p->departure, sizeof(p->departure));
    inputString("到达时间: ", p->arrival, sizeof(p->arrival));
    inputString("票价: ", temp, sizeof(temp));
    p->price = atof(temp);//atof将字符串转化为浮点数
    inputString("剩余票数: ", temp, sizeof(temp));
    p->tickets = atoi(temp);
    //将新节点插入链表
    p->next = head;
    head = p;

    printf("添加成功！\n");
}
//分配内存创建一个新的 Train 结构体节点。
//读取车次ID并检查是否已存在于链表中，如果存在则释放内存并返回。
//读取其他车次信息，如始发站、终点站等。
//将新节点插入到链表头部。

// 查询
void SearchTrain() //SearchTrain函数实现了根据用户选择的查询类型（按车次或按城市）和输入的关键词，
                   //在链表中查找并显示符合条件的车次信息的功能。
{
    char key[20];
    char temp[10];
    int type, found = 0;

    inputString("按车次(1)或城市(2)查询: ", temp, sizeof(temp));
    type = atoi(temp);
    inputString("请输入关键词: ", key, sizeof(key));

    Train* cur = head;
    while (cur)
    {
        if ((type == 1 && strcmp(cur->id, key) == 0) ||
            (type == 2 && strcmp(cur->end, key) == 0))
        {
            printf("%s %s->%s 出发: %s 到达: %s 票价: %.2f 剩余: %d\n",
            cur->id, cur->start, cur->end, cur->departure,
            cur->arrival, cur->price, cur->tickets);
            found = 1;
        }
        cur = cur->next;//将cur指针移动到链表的下一节点
    }

    if (!found) 
    {
        printf("未找到相关信息！\n");
    }
}

// 订票
void BookTicket() //BookTicket  函数实现了根据用户输入的目的地城市查找可订票车次，然后根据用户输入的车次
                  //ID 确认车次并完成订票操作的功能，同时处理了各种可能的情况并给出相应的提示信息。
{
    char city[20], id[10];
    int found = 0;
    char name[20];
    char idCard[19];

    inputString("请输入目的地城市: ", city, sizeof(city));

    Train* cur = head;
    while (cur) {
        if (strcmp(cur->end, city) == 0 && cur->tickets > 0) {
            printf("%s %s->%s 出发: %s 票价: %.2f 剩余: %d\n",
                cur->id, cur->start, cur->end, cur->departure,
                cur->price, cur->tickets);
            found = 1;
        }
        cur = cur->next;
    }

    if (!found) {
        printf("无可订票车次。\n");
        return;
    }

    inputString("请输入要订票的车次ID: ", id, sizeof(id));
    cur = head;
    while (cur) {
        if (strcmp(cur->id, id) == 0 && cur->tickets > 0) {
            inputString("请输入您的姓名: ", name, sizeof(name));
            inputString("请输入您的身份证号: ", idCard, sizeof(idCard));
            cur->tickets--;
            printf("订票成功！姓名: %s，身份证号: %s\n", name, idCard);
            return;
        }
        cur = cur->next;
    }
    printf("订票失败，可能无票或车次不存在。\n");
}

// 修改车次
void ModifyTrain() {
    char id[10], temp[100];
    inputString("请输入要修改的车次: ", id, sizeof(id));

    Train* cur = head;
    while (cur) {
        if (strcmp(cur->id, id) == 0) {
            inputString("新始发站: ", cur->start, sizeof(cur->start));
            inputString("新终点站: ", cur->end, sizeof(cur->end));
            inputString("新出发时间: ", cur->departure, sizeof(cur->departure));
            inputString("新到达时间: ", cur->arrival, sizeof(cur->arrival));
            inputString("新票价: ", temp, sizeof(temp));
            cur->price = atof(temp);
            inputString("新剩余票数: ", temp, sizeof(temp));
            cur->tickets = atoi(temp);
            printf("修改成功！\n");
            return;
        }
        cur = cur->next;
    }
    printf("未找到该车次。\n");
}

// 显示所有车次
void ShowTrains() {
    Train* cur = head;
    if (!cur) {
        printf("暂无车次信息。\n");
        return;
    }
    while (cur) {
        printf("%s %s->%s 出发: %s 到达: %s 票价: %.2f 剩余: %d\n",
            cur->id, cur->start, cur->end, cur->departure,
            cur->arrival, cur->price, cur->tickets);
        cur = cur->next;
    }
}

// 保存信息
void SaveToFile() 
{
    //一。文件打开
    FILE* fp = fopen("trains.txt", "w");//使用fopen函数以写入模式（ "w" ）打开名为trains.txt的文件
                      //，并将文件指针赋值给fp 。如果文件打开失败（fopen返回NULL），则执行if块中的代码。
    if (!fp) {
        printf("无法保存文件。\n");
        return;
    }
    //检查文件指针  fp  是否为  NULL ，如果是
    //，则打印提示信息 “无法保存文件。” 并通过  return  语句结束函数，不再进行后续操作
    //二。写入数据
    Train* cur = head;
    while (cur) {
        fprintf(fp, "%s %s %s %s %s %.2f %d\n",
            cur->id, cur->start, cur->end, cur->departure,
            cur->arrival, cur->price, cur->tickets);//使用fprintf函数将当前车次节点的信息（车次 ID、
                 //始发站、终点站、出发时间、到达时间、票价、剩余票数）按照
                 //指定格式写入到文件中。每个信息之间用空格分隔，最后换行。
        cur = cur->next;
    }
    //三。关闭文件并提示
    fclose(fp);//使用  fclose  函数关闭打开的文件，释放系统资源。
    printf("保存成功！\n");
}

// 读取信息
void LoadFromFile() 
{
    //一。文件打开（同上）
    FILE* fp = fopen("trains.txt", "r");
    if (!fp) return;
    //二。读取数据并构建链表
    while (!feof(fp))  //只要文件未到达结尾（ feof  函数返回  0 ），就进入循环。
    {
        Train* p = (Train*)malloc(sizeof(Train));
        if (fscanf(fp, "%s %s %s %s %s %f %d",
            p->id, p->start, p->end, p->departure,
            p->arrival, &p->price, &p->tickets) == 7) 
            //使用  fscanf  函数从文件中读取一行数据，按照格式分别赋值给新节点  p  的各个成员（车次 ID、始发站、终点站、
            //出发时间、到达时间、票价、剩余票数）。如果fscanf成功读取并赋值了 7 个数据（返回值为 7），则执行以下操作：
        {
            p->next = head;//将新节点  p  的  next  指针指向当前链表的头节点  head 。
            head = p;// 将链表的头指针  head  更新为指向新节点  p ，这样新节点就成为了链表的第一个节点。
        }
        else 
        {
            free(p);//使用  free  函数释放为新节点  p  分配的内存。
        }
    }
    //三。关闭文件
    fclose(fp);
}

// 释放内存
void FreeList()
{
    //初始化指针
    Train* cur = head;
    //释放内存
    while (cur) // 只要  cur  不为  NULL ，就进入循环，遍历链表中的每个节点
    {
        Train* temp = cur;// 声明一个临时指针
        cur = cur->next;
        free(temp); //使用  free  函数释放  temp  所指向的节点的内存，即释放链表中的一个节点。
    }
}
//暂停程序执行等待用户输入
void Pause() 
{
    printf("按 Enter 键继续...");
    getchar();
}
//主函数
int main() {
    LoadFromFile();//先从文件中读取车次信息
    int choice;
    char temp[10];

    do {
        system(CLEAR);//清屏，史桌面更加整洁
        printf("=== 火车票订票系统 ===\n");
        printf("1. 添加车次\n");
        printf("2. 查询车次\n");
        printf("3. 订票\n");
        printf("4. 修改车次\n");
        printf("5. 显示所有车次\n");
        printf("6. 保存信息\n");
        printf("0. 退出系统\n");
        inputString("请选择操作: ", temp, sizeof(temp));
        choice = atoi(temp);

        switch (choice) {
        case 1: AddTrain(); break;
        case 2: SearchTrain(); break;
        case 3: BookTicket(); break;
        case 4: ModifyTrain(); break;
        case 5: ShowTrains(); break;
        case 6: SaveToFile(); break;
        case 0: SaveToFile(); break;
        default: printf("无效选择！\n"); break;
        }
        Pause();
    } while (choice != 0);

    FreeList();//释放链表占用的内存
    return 0;
}